package com.base.commons.utils;

import com.base.commons.emums.OperateType;
import com.base.commons.tools.utils.StringUtils;
import com.base.commons.annotation.BusinessLog;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.util.CollectionUtils;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 业务日志工具类
 * @author wangwenji
 * @since 2021-04-13
 */
public class BusinessLogUtils {

    /**
     * 生成业务操作日志
     * @param operateType   操作类型枚举 ；例：OperateType.ADD
     * @param oldObject     旧业务对象
     * @param newObject     新业务对象
     * @param ignoreArr     业务对象忽略属性
     * @return
     */
    public static String operateLog(OperateType operateType, Object oldObject, Object newObject, String[] ignoreArr) throws Exception {
        if(null != oldObject && null != newObject && oldObject.getClass() != newObject.getClass())
            return null;
        String logInfo = null;
        List<String> ignoreList = null;
        if(ignoreArr != null && ignoreArr.length > 0){
            ignoreList = Arrays.asList(ignoreArr);
        }

        StringBuffer opreateLog = new StringBuffer();
        // 新增
        if(OperateType.ADD.name().equals(operateType.name())
                || OperateType.IMPORT.name().equals(operateType.name())
                || OperateType.DELETE.name().equals(operateType.name())){
            if(null == newObject)
                return null;

            Map<String,Object> attrMap = (Map)generateLog(operateType,oldObject,newObject,ignoreList,opreateLog);
            // 处理新增类中存在集合属性
            if(!CollectionUtils.isEmpty(attrMap)) {
                opreateLog.append(";");
                // 遍历集合属性
                for (String attrName : attrMap.keySet()) {
                    Map<String,Object> dataMap = (Map)attrMap.get(attrName);
                    // 遍历title
                    for(String title : dataMap.keySet()){
                        ArrayList objList = (ArrayList) dataMap.get(title);
                        if (!CollectionUtils.isEmpty(objList)) {
                            opreateLog.append(title);
                            opreateLog.append(":[");
                            // 遍历集合对象中属性
                            for (Object object : objList) {
                                opreateLog.append("{");
                                generateLog(operateType, oldObject, object, ignoreList, opreateLog);
                                opreateLog.append("};");
                            }
                            opreateLog = opreateLog.deleteCharAt(opreateLog.length() - 1);
                            opreateLog.append("]");
                        }
                        objList.clear();
                    }
                    dataMap.clear();
                }
            }
            // 清理集合对象释放内存
            attrMap.clear();
        }else if(OperateType.UPDATE.name().equals(operateType.name())
                || OperateType.STOP_START.name().equals(operateType.name())
                || OperateType.AUDIT.name().equals(operateType.name())){
            if(null == oldObject || null == newObject)
                return null;

            Map<String,Object> attrMap = (Map)generateLog(operateType,oldObject,newObject,ignoreList,opreateLog);
            // 处理修改类中存在集合属性
            if(!CollectionUtils.isEmpty(attrMap)) {
                opreateLog.append(";");
                // 遍历集合属性
                for (String attrName : attrMap.keySet()) {
                    Map<String, List<Map<Object,Object>>> dataMap = (Map)attrMap.get(attrName);
                    // 遍历title
                    for(String title : dataMap.keySet()){
                        List<Map<Object, Object>> attrList = dataMap.get(title);
                        if (!CollectionUtils.isEmpty(attrList)) {
                            opreateLog.append(title);
                            opreateLog.append(":[");
                            Map<Object, Object> newAttrMap = attrList.get(0);
                            Map<Object, Object> oldAttrMap = attrList.get(1);
                            //if(CollectionUtils.isEmpty(newAttrMap))
                            // 处理差集
                            for (Object primaryKey : newAttrMap.keySet()) {
                                Object oldObj = oldAttrMap.get(primaryKey);
                                // 旧找新，找不到视为新增
                                if (null == oldObj) {
                                    opreateLog.append("{新增").append(title).append(":{");
                                    generateLog(OperateType.ADD, null, newAttrMap.get(primaryKey), ignoreList, opreateLog);
                                    opreateLog.append("}};");
                                } else {
                                    // 找得到就新旧对比找对差集
                                    StringBuffer updateMsg = new StringBuffer();
                                    Object updateObj = generateLog(operateType, oldObj, newAttrMap.get(primaryKey), ignoreList, updateMsg);
                                    if(null != updateObj){
                                        opreateLog.append("{修改").append(title).append(":{");
                                        opreateLog.append(updateMsg);
                                        opreateLog.append("}};");
                                    }
                                }
                            }

                            for (Object primaryKey : oldAttrMap.keySet()) {
                                Object newObj = newAttrMap.get(primaryKey);
                                if (null == newObj) {
                                    // 新找旧，找不到视为删除
                                    PropertyDescriptor[] newPds = Introspector.getBeanInfo(oldAttrMap.get(primaryKey).getClass(),
                                            Object.class).getPropertyDescriptors();
                                    PropertyDescriptor[] orderPds = getOrderedField(newPds,oldAttrMap.get(primaryKey));
                                    if (null != orderPds) {
                                        opreateLog.append("{删除").append(title).append(":{");
                                        for (PropertyDescriptor pd : orderPds) {
                                            // 忽略属性
                                            if(ignoreList != null
                                                    && ignoreList.contains(pd.getName()))
                                                continue;
                                            try{
                                                Field field = oldAttrMap.get(primaryKey).getClass().getDeclaredField(pd.getName());
                                                Annotation annotation = field.getAnnotation(BusinessLog.class);
                                                if (null != annotation
                                                        && isGroup(field.getAnnotation(BusinessLog.class),operateType)) {
                                                    Method readMethod = pd.getReadMethod();
                                                    Object obj = readMethod.invoke(oldAttrMap.get(primaryKey));

                                                    opreateLog.append(field.getAnnotation(BusinessLog.class).title());
                                                    opreateLog.append("[");
                                                    if (isBaseType(readMethod.getReturnType(), true)) {
                                                        opreateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(),obj));
                                                    }else if(obj instanceof Date){
                                                        if(null != obj &&
                                                                !StringUtils.isEmpty(field.getAnnotation(BusinessLog.class).format())){
                                                            DateFormat dFormat = new SimpleDateFormat(field.getAnnotation(BusinessLog.class).format());
                                                            opreateLog.append(dFormat.format(obj));
                                                        }else{
                                                            opreateLog.append(nullConver(obj));
                                                        }
                                                    }
                                                    opreateLog.append("];");
                                                }
                                            }catch (NoSuchFieldException e){
                                                // 找不到属性时忽略
                                                continue;
                                            }
                                        }
                                        opreateLog.append("}};");
                                    }
                                }
                            }
                            opreateLog = opreateLog.deleteCharAt(opreateLog.length() - 1);
                            opreateLog.append("]");
                            newAttrMap.clear();
                            oldAttrMap.clear();
                        }
                        attrList.clear();
                    }
                    dataMap.clear();
                }
            }
            if(!CollectionUtils.isEmpty(attrMap))
                attrMap.clear();
        }

        if(StringUtils.isNotEmpty(opreateLog)){
            logInfo = opreateLog.toString();
        }
        return logInfo;
    }

    /**
     * 对比差集生成操作日志
     * @param operateType 操作类型
     * @param oldObject   旧的操作对象
     * @param newObject   新的操作对象
     * @param ignoreList  业务对象忽略属性
     * @param opreateLog
     * @return
     * @throws Exception
     */
    private static Object generateLog(OperateType operateType,Object oldObject, Object newObject, List<String> ignoreList,StringBuffer opreateLog) throws Exception{
        Map<String,Object> attrMap = new HashMap<String, Object>();
        if(StringUtils.isEmpty(opreateLog))
            opreateLog.append(operateType.getText()).append("：");
        // 新增
        if(OperateType.ADD.name().equals(operateType.name())
                || OperateType.IMPORT.name().equals(operateType.name())
                || OperateType.DELETE.name().equals(operateType.name())){

            PropertyDescriptor[] pds = Introspector.getBeanInfo(newObject.getClass(),
                    Object.class).getPropertyDescriptors();
            PropertyDescriptor[] orderPds = getOrderedField(pds,newObject);
            if(null != pds){
                for (PropertyDescriptor pd : orderPds) {
                    String attrName = pd.getName();
                    // 忽略属性
                    if(ignoreList != null
                            && ignoreList.contains(attrName))
                        continue;
                    try{
                        Field field = newObject.getClass().getDeclaredField(attrName);
                        Annotation annotation = field.getAnnotation(BusinessLog.class);
                        if(null != annotation
                                && isGroup(field.getAnnotation(BusinessLog.class),operateType)) {
                            Method readMethod = pd.getReadMethod();
                            Object obj = readMethod.invoke(newObject);

                            if(isBaseType(readMethod.getReturnType(),true)) {
                                opreateLog.append(field.getAnnotation(BusinessLog.class).title());
                                opreateLog.append("[");
                                opreateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(),obj));
                                opreateLog.append("];");
                            }else if(obj instanceof Date){
                                opreateLog.append(field.getAnnotation(BusinessLog.class).title());
                                opreateLog.append("[");
                                if(null != obj && !StringUtils.isEmpty(field.getAnnotation(BusinessLog.class).format())){
                                    DateFormat dFormat = new SimpleDateFormat(field.getAnnotation(BusinessLog.class).format());
                                    opreateLog.append(dFormat.format(obj));
                                }else{
                                    opreateLog.append(nullConver(obj));
                                }
                                opreateLog.append("];");
                            }else if(obj instanceof ArrayList){
                                // 子集合放到最后统一处理
                                Map<String,Object> dataMap = new HashMap<String, Object>();
                                dataMap.put(field.getAnnotation(BusinessLog.class).title(),obj);
                                attrMap.put(attrName,dataMap);
                            }
                        }
                    }catch (NoSuchFieldException e){
                        // 找不到属性时忽略
                        continue;
                    }
                }
            }
        }else if(OperateType.UPDATE.name().equals(operateType.name())
                || OperateType.STOP_START.name().equals(operateType.name())
                || OperateType.AUDIT.name().equals(operateType.name())){
            // 新旧对比差异
            Map<String, List<Object>> compareFieldMap = CompareUtil.compareFields(oldObject,newObject,ignoreList);
            if(CollectionUtils.isEmpty(compareFieldMap)) {
                if(StringUtils.isNotEmpty(opreateLog)) {
                    opreateLog.append("没有编辑任何内容；");
                }
                return null;
            }

            PropertyDescriptor[] pds = Introspector.getBeanInfo(newObject.getClass(),
                    Object.class).getPropertyDescriptors();
            PropertyDescriptor[] orderPds = getOrderedField(pds,newObject);
            if(null != orderPds){
                List<T> objList = new ArrayList<T>();
                StringBuffer updateLog = new StringBuffer();
                for (PropertyDescriptor pd : orderPds) {
                    String attrName = pd.getName();
                    // 忽略属性
                    if(ignoreList != null
                            && ignoreList.contains(attrName))
                        continue;

                    try{
                        Field field = newObject.getClass().getDeclaredField(attrName);
                        Annotation annotation = field.getAnnotation(BusinessLog.class);
                        // 判断是否被注解标记，只记录被标记的属性日志
                        if(null != annotation
                                && isGroup(field.getAnnotation(BusinessLog.class),operateType)) {

                            Method readMethod = pd.getReadMethod();
                            Object newObj = readMethod.invoke(newObject);
                            if(field.getAnnotation(BusinessLog.class).primaryKey()){
                                opreateLog.append(field.getAnnotation(BusinessLog.class).title());
                                opreateLog.append("(");
                                opreateLog.append(newObj);
                                opreateLog.append(")-");
                            }
                            List<Object> compareList = compareFieldMap.get(attrName);
                            // 存在差异才记录更新日志
                            if(!CollectionUtils.isEmpty(compareList)){

                                if(OperateType.UPDATE.name().equals(operateType.name())) {
                                    if (isBaseType(readMethod.getReturnType(), true)) {
                                        updateLog.append(field.getAnnotation(BusinessLog.class).title());
                                        updateLog.append("由[");
                                        updateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(), compareList.get(0)));
                                        updateLog.append("]");
                                        updateLog.append("改为[");
                                        updateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(), compareList.get(1)));
                                        updateLog.append("];");
                                    } else if (newObj instanceof Date) {
                                        updateLog.append(field.getAnnotation(BusinessLog.class).title());
                                        updateLog.append("由[");
                                        if (null != compareList.get(0) &&
                                                !StringUtils.isEmpty(field.getAnnotation(BusinessLog.class).format())) {
                                            DateFormat dFormat = new SimpleDateFormat(field.getAnnotation(BusinessLog.class).format());
                                            updateLog.append(dFormat.format(compareList.get(0)));
                                        } else {
                                            updateLog.append(nullConver(compareList.get(0)));
                                        }
                                        updateLog.append("]");
                                        updateLog.append("改为[");
                                        if (null != compareList.get(1) &&
                                                !StringUtils.isEmpty(field.getAnnotation(BusinessLog.class).format())) {
                                            DateFormat dFormat = new SimpleDateFormat(field.getAnnotation(BusinessLog.class).format());
                                            updateLog.append(dFormat.format(compareList.get(1)));
                                        } else {
                                            updateLog.append(nullConver(compareList.get(1)));
                                        }
                                        updateLog.append("];");
                                    } else if ((newObj instanceof ArrayList)) {
                                        Object oldObj = readMethod.invoke(oldObject);
                                        // 将当前属性的集合丢入map中，最后统一处理
                                        List<Map<Object, Object>> primaryList = new ArrayList<Map<Object, Object>>();

                                        Map<Object, Object> newPrimaryKeyMap = getDiffMap(newObj, operateType);
                                        primaryList.add(newPrimaryKeyMap);

                                        Map<Object, Object> oldPrimaryKeyMap = getDiffMap(oldObj, operateType);
                                        primaryList.add(oldPrimaryKeyMap);

                                        Map<String, List<Map<Object, Object>>> dataMap = new HashMap<String, List<Map<Object, Object>>>();
                                        dataMap.put(field.getAnnotation(BusinessLog.class).title(), primaryList);
                                        attrMap.put(attrName, dataMap);
                                    }
                                }else{
                                    updateLog.append(field.getAnnotation(BusinessLog.class).title());
                                    updateLog.append("由[");
                                    updateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(), compareList.get(0)));
                                    updateLog.append("]");
                                    updateLog.append("操作为[");
                                    updateLog.append(getReplace(field.getAnnotation(BusinessLog.class).replace(), compareList.get(1)));
                                    updateLog.append("];");
                                    // 审核、启停只关注状态字段即可，如需关注其它字段放开break即可
                                    break;
                                }
                            }
                        }
                    }catch (NoSuchFieldException e){
                        // 找不到属性时忽略
                        continue;
                    }
                }
                opreateLog.append(updateLog);
            }
        }
        opreateLog = opreateLog.deleteCharAt(opreateLog.length() - 1);
        return attrMap;
    }

    /**
     * 属性排序
     * @param pds
     * @param obj
     * @return
     */
    public static PropertyDescriptor[] getOrderedField(PropertyDescriptor[] pds,Object obj){
        if(pds == null || null == obj){
            return pds;
        }
        for(int i=0;i<pds.length-1;i++)
        {
            for(int j=0;j<pds.length-1-i;j++)
            {
                try {
                    Field field1 = obj.getClass().getDeclaredField(pds[j].getName());
                    String order1 = field1.getAnnotation(BusinessLog.class).order();
                    if(null == order1 || StringUtils.isEmpty(order1)){
                        continue;
                    }
                    Field field2 = obj.getClass().getDeclaredField(pds[j+1].getName());
                    String order2= field2.getAnnotation(BusinessLog.class).order();
                    if(Float.parseFloat(order1)>Float.parseFloat(order2))
                    {
                        PropertyDescriptor temp=pds[j];
                        pds[j]=pds[j+1];
                        pds[j+1]=temp;
                    }
                }catch (Exception e){
                    continue;
                }
            }
        }
        return pds;
    }

    /**
     * 获取差集
     * @param object
     * @param operateType
     * @return
     * @throws Exception
     */
    private static Map<Object,Object> getDiffMap(Object object,OperateType operateType)throws Exception{
        Map<Object,Object> primaryKeyMap = new HashMap<Object, Object>();
        for (Object attrList : (ArrayList) object) {
            PropertyDescriptor[] pds = Introspector.getBeanInfo(attrList.getClass(),
                    Object.class).getPropertyDescriptors();
            PropertyDescriptor[] orderPds = getOrderedField(pds,attrList);
            if(null != orderPds) {
                for (PropertyDescriptor attrPd : orderPds) {
                    try{
                        Field field = attrList.getClass().getDeclaredField(attrPd.getName());
                        Annotation oldAnnotation = field.getAnnotation(BusinessLog.class);
                        // 查找主键字段作为key，便于后面查找两个集合的对比差集
                        if (null != oldAnnotation
                                && isGroup(field.getAnnotation(BusinessLog.class),operateType)) {
                            Method oldAttrMethod = attrPd.getReadMethod();
                            Object obj = oldAttrMethod.invoke(attrList);
                            if(field.getAnnotation(BusinessLog.class).primaryKey()){
                                primaryKeyMap.put(obj,attrList);
                                continue;
                            }
                        }
                    }catch (NoSuchFieldException e){
                        // 找不到属性时忽略
                        continue;
                    }
                }
            }
        }
        return primaryKeyMap;
    }

    /**
     * 是否基础数据类型
     * @param className
     * @param incString 是否包含String
     * @return
     */
    public static boolean isBaseType(Class className, boolean incString) {
        if (incString && className.equals(String.class)) {
            return true;
        }
        return className.equals(Integer.class) ||
                className.equals(int.class) ||
                className.equals(Byte.class) ||
                className.equals(byte.class) ||
                className.equals(Long.class) ||
                className.equals(long.class) ||
                className.equals(Double.class) ||
                className.equals(double.class) ||
                className.equals(Float.class) ||
                className.equals(float.class) ||
                className.equals(Character.class) ||
                className.equals(char.class) ||
                className.equals(Short.class) ||
                className.equals(short.class) ||
                className.equals(Boolean.class) ||
                className.equals(BigDecimal.class) ||
                className.equals(boolean.class);
    }

    /**
     * 注解值转换
     * @param replaces
     * @param replaceKey
     * @return
     */
    public static String getReplace(String[] replaces,Object replaceKey){
        if(null == replaces || replaces.length == 0 || null == replaceKey)
            return nullConver(replaceKey);
        String replaceValue = String.valueOf(replaceKey);
        for (String replace : replaces) {
            String [] replaceArry = replace.split("_");
            if(replaceArry[1].equals(String.valueOf(replaceKey))){
                replaceValue = replaceArry[0];
                continue;
            }
        }
        return replaceValue;
    }

    /**
     * 空值转换
     * @param param
     * @return
     */
    public static String nullConver(Object param){
        if(null == param || StringUtils.isEmpty(String.valueOf(param))){
            return "";
        }else{
            return String.valueOf(param);
        }
    }

    /**
     * 获取分组是否属于分组类型
     * @param operationLog
     * @param operateType
     * @return
     */
    public static boolean isGroup(BusinessLog operationLog,OperateType operateType){
        OperateType [] operateTypes = operationLog.groups();
        if(null == operateTypes || operateTypes.length == 0)
            return false;
        boolean isGroup = false;
        for (OperateType type : operateTypes) {
            if(operateType.name().equals(type.name())){
                isGroup = true;
                continue;
            }
        }
        return isGroup;
    }
}